using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace Deferred_Rendering
{
    public class Environment
    {
        struct ModelTextured
        {
            public Model Model;
            public Texture2D Colour, Normals, Specular;
            public Matrix modelWorldMatrix;
        }

        private Game game;

        private Camera camera;

        private Model[] models;

        private ModelTextured[] modelList;

        private List<ModelTextured> texModelList;

        private Vector3 modelOffset;

        private Effect gbufferEffect;
        private Effect perPixelPhongShader;

        private Lights lights;

        private int modelNumber;

        private int tempLightNum;

        public Environment(Game game) 
        { 
            this.game = game;
        } 

        public void InitializeEnvironment() 
        {
            modelOffset = new Vector3(-150.0f, -10.0f, -150.0f);

            modelNumber = 1;

            tempLightNum = 0;

            models = new Model[2];
            modelList = new ModelTextured[2];

            texModelList = new List<ModelTextured>();

            models[0] = game.Content.Load<Model>("Models\\ship1");
            models[1] = game.Content.Load<Model>("Models\\Ground");

            modelList[0].Model = game.Content.Load<Model>("Models\\ship1");
            modelList[1].Model = game.Content.Load<Model>("Models\\Ground");

            modelList[0].Colour = game.Content.Load<Texture2D>("Models\\ship1_c");
            modelList[0].Normals = game.Content.Load<Texture2D>("Models\\ship1_n");
            modelList[0].Specular = game.Content.Load<Texture2D>("Models\\ship1_s");

            modelList[1].Colour = game.Content.Load<Texture2D>("Models\\ground_diffuse");
            modelList[1].Normals = game.Content.Load<Texture2D>("Models\\ground_normal");
            modelList[1].Specular = game.Content.Load<Texture2D>("Models\\ground_specular");

            modelList[0].modelWorldMatrix = Matrix.CreateTranslation(-150.0f, -10.0f, -150.0f) * Matrix.CreateScale(0.5f, 0.5f, 0.5f);

            texModelList.Add(modelList[0]);

            camera = (Camera)game.Services.GetService(typeof(Camera));
            lights = (Lights)game.Services.GetService(typeof(Lights));

            gbufferEffect = game.Content.Load<Effect>("Deferred Renderer Shader Files\\RenderGBuffer");
            perPixelPhongShader = game.Content.Load<Effect>("Forward Renderer Shader Files\\PerPixelPhong");
        }

        public void addModel()
        {
            if (texModelList.Count < 42)
            {
                ModelTextured modelTemp = modelList[0];

                if (modelOffset.X < 150.0f)
                {
                    modelOffset.X += 50.0f;
                }
                else
                {
                    modelOffset.Z += 50.0f;
                    modelOffset.X = -150.0f;
                }

                modelTemp.modelWorldMatrix = Matrix.CreateTranslation(modelOffset) * Matrix.CreateScale(0.5f, 0.5f, 0.5f);
                texModelList.Add(modelTemp);

                modelNumber++;
            }
        }

        public void removeModel()
        {
            if (texModelList.Count > 1)
            {
                if (modelOffset.X > -150.0f)
                {
                    modelOffset.X -= 50.0f;
                }
                else
                {
                    modelOffset.Z -= 50.0f;
                    modelOffset.X = 150.0f;
                }

                texModelList.RemoveAt(texModelList.Count-1);
                modelNumber--;
            }
        }

        private void DrawModelDeferred(ModelTextured model, Matrix world, Camera camera)
        {
            foreach (ModelMesh mesh in model.Model.Meshes)
            {
                foreach (Effect effect in mesh.Effects)
                {
                    effect.Parameters["World"].SetValue(world);
                    effect.Parameters["View"].SetValue(camera.View);
                    effect.Parameters["Projection"].SetValue(camera.Projection);
                }

                mesh.Draw();
            }
        }

        private void DrawModelForward(ModelTextured model, Matrix world, Camera camera)
        {
            EffectParameterCollection forwardParameters = perPixelPhongShader.Parameters["lights"].Elements;

            if (lights.LightList.Count < 40)
            {
                tempLightNum = lights.LightList.Count;
            }
            else if (lights.LightList.Count >= 40)
            {
                tempLightNum = 40;
            }

            for (int i = 0; i < tempLightNum; i++)
            {
                EffectParameterCollection lightCollection = forwardParameters[i].StructureMembers;
                lightCollection["position"].SetValue(lights.LightList[i].Position);
                lightCollection["colour"].SetValue(lights.LightList[i].Colour.ToVector4());
                lightCollection["radius"].SetValue(lights.LightList[i].Radius);
            } 

            foreach (Light light in lights.LightList)
            {
                perPixelPhongShader.Parameters["World"].SetValue(world);
                perPixelPhongShader.Parameters["View"].SetValue(camera.View);
                perPixelPhongShader.Parameters["Projection"].SetValue(camera.Projection);
                perPixelPhongShader.Parameters["CameraPosition"].SetValue(camera.Position);

                perPixelPhongShader.Parameters["Texture"].SetValue(model.Colour);
                perPixelPhongShader.Parameters["NormalMap"].SetValue(model.Normals);
                perPixelPhongShader.Parameters["SpecularMap"].SetValue(model.Specular);

                perPixelPhongShader.Parameters["lightNum"].SetValue(tempLightNum);

                perPixelPhongShader.Parameters["SpecularLightColour"].SetValue(Color.White.ToVector4()); 

                perPixelPhongShader.Begin();

                perPixelPhongShader.CurrentTechnique.Passes[0].Begin();

                foreach (ModelMesh mesh in model.Model.Meshes)
                {
                    foreach (ModelMeshPart meshPart in mesh.MeshParts)
                    {
                        game.GraphicsDevice.VertexDeclaration = meshPart.VertexDeclaration;
                        game.GraphicsDevice.Vertices[0].SetSource(mesh.VertexBuffer, meshPart.StreamOffset, meshPart.VertexStride);
                        game.GraphicsDevice.Indices = mesh.IndexBuffer;
                        game.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, meshPart.BaseVertex, 0, meshPart.NumVertices, meshPart.StartIndex, meshPart.PrimitiveCount);
                    }
                }

                perPixelPhongShader.CurrentTechnique.Passes[0].End();

                perPixelPhongShader.End();
            }
        }

        public void DrawEnvironmentDeferred(Camera camera, GameTime gameTime) 
        {
            game.GraphicsDevice.RenderState.DepthBufferEnable = true;
            game.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
            game.GraphicsDevice.RenderState.AlphaBlendEnable = false;


            DrawModelDeferred(modelList[1], Matrix.CreateTranslation(0, -10, 0), camera);

            for (int i = 0; i < texModelList.Count; i++)
            {
                DrawModelDeferred(texModelList[i], texModelList[i].modelWorldMatrix, camera);
            }
        }

        public void DrawEnvironmentForward(Camera camera, GameTime gameTime)
        {
            game.GraphicsDevice.RenderState.DepthBufferEnable = true;
            game.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
            game.GraphicsDevice.RenderState.AlphaBlendEnable = false;

            DrawModelForward(modelList[1], Matrix.CreateTranslation(0, -10, 0), camera);

            for (int i = 0; i < texModelList.Count; i++)
            {
                DrawModelForward(texModelList[i], texModelList[i].modelWorldMatrix, camera);
            }
        }

        public int ModelNumber
        {
            get { return modelNumber; }
            set { modelNumber = value; }
        }
    }
}